#pragma once
#ifndef LIT__SERIALISER__HH__
#define LIT__SERIALISER__HH__

#include <LitString.h>
#include <LitStream.h>
#include <LitNamed.hpp>

#include <iostream>
#include <tinyxml.h>

namespace Lit
{
	template<typename T>
	class Serialiser : public Named<false>
	{
	protected:
		float mVersion;
	public:
		typename typedef T type;
		
		Serialiser( const String &name="", float version = 1.0f ) 
#ifdef LIT_DEBUG
			: Named( String(name) + " v" + std::to_string(version).c_str() + " r" + std::to_string(PROJECT_VERSION_REVISION).c_str() )
#else 
			: Named( String(name) + " v" + std::to_string(version) )
#endif
			, mVersion(version)
		{}

		Serialiser( const Serialiser<T>& rk )
			: mVersion( rk.mVersion )
			, Named( rk )
		{}

		virtual ~Serialiser()
		{}

		Serialiser<T>& operator=( const Serialiser<T>& rk )
		{
			mVersion = rk.mVersion;
			Named::operator=( rk );
			return *this;
		}
		virtual const T& toFile( const Lit::FilePath &path, const T& in, bool binary = false )
		{
			FileStream strm(path.str(), binary?Stream::BinaryWrite:Stream::TextWrite);
			toStream( StreamPtr( &strm ), in );
			return in;
		}
		virtual T& fromFile( const Lit::FilePath& path, T& out, bool binary = false )
		{
			FileStream strm(path.str(), binary?Stream::BinaryRead:Stream::TextRead);
			fromStream( StreamPtr( &strm ), out );
			return out;
		}
		const float& getVersion() const{ return mVersion; }

		virtual StreamPtr& fromStream( StreamPtr& stream, T& out ) = 0;
		virtual StreamPtr& toStream( StreamPtr& stream, const T& in ) = 0;
	};

	template<typename T>
	class XmlSerialiser : public Serialiser<T>
	{
		String mEncoding;
		bool mStandalone;
	public:
		XmlSerialiser( const String &name, float version = 1.0f, const String &encoding = String("UTF-8") ) 
			: mEncoding(encoding)
			, Serialiser(name, version)
		{}
		XmlSerialiser( const XmlSerialiser& rk )
			: Serialiser( rk )
			, mEncoding( rk.mEncoding )
			, mStandalone( rk.mStandalone )
		{}
		~XmlSerialiser(){}
		
		XmlSerialiser<T>& operator=( const XmlSerialiser<T>& rk )
		{
			mEncoding = rk.mEncoding;
			mStandalone = rk.mStandalone;
			Serialiser::operator=( rk );
			return *this;
		}
		virtual void saveObject( TiXmlDocument * doc, const T& in ) = 0;
		virtual void loadObject( TiXmlDocument * doc, const String &base_dir, T& out ) = 0;
		
		virtual StreamPtr& fromStream( StreamPtr& stream, T& out ){
			try
			{
				TiXmlDocument doc;
				if( !stream )
					throw Exception( EXC_RUNTIME_ERROR, "StreamPtr is invalid", __FUNCTION__, __LINE__, __FILE__  );
				if( !stream->valid() || stream->eof() )
					throw Exception( EXC_RUNTIME_ERROR, "Invalid stream", __FUNCTION__, __LINE__, __FILE__ );

				String f(stream->readAll());
				if( f.empty() || f[0] == '\0' )
					throw Exception( EXC_RUNTIME_ERROR, "EOF reached, stream is empty", __FUNCTION__, __LINE__, __FILE__ );
				 
				const char *cret = doc.Parse( f.data() );
				String ret(cret?cret:"");
				if( !ret.empty() ) 
					throw Exception(EXC_RUNTIME_ERROR, "Cannot parse xml nodes: "+ret, __FUNCTION__, __LINE__, __FILE__ );
				
				FilePath p(FilePath(stream->getName()).dirName()+"/");
				
				loadObject( &doc, p, out );
			}
			catch( Lit::Exception e )
			{
				std::cerr << "Cannot import object from stream '"<<mName.data()<<"' because "<< e.full().data();
				throw StreamError( e.message(), (stream?stream->getName().data():"<invalid>"), e.function(), e.line(), e.file() );
			}
			catch( std::exception e )
			{
				std::cerr << "Cannot import object from stream '"<<mName.data()<<"' because "<<String(e.what()).data();
				throw StreamError( e.what(), (stream?stream->getName():"<invalid>"), __FUNCTION__, __LINE__, __FILE__  );
			}
			catch(...)
			{ throw; }
			return stream;
		}
		virtual StreamPtr& toStream( StreamPtr& stream, const T& in ){
			try
			{
				TiXmlDocument doc;
				doc.LinkEndChild( new TiXmlComment( ("Serialised using "+mName).data() ) );
				doc.LinkEndChild( new TiXmlComment( "    CoreLibrary v"PROJECT_VERSION_STRING ) );
				doc.LinkEndChild( new TiXmlDeclaration( Lit::String(std::to_string(mVersion).c_str()).data(), mEncoding.data(), mStandalone?"yes":"no" ) );
			
				saveObject( &doc, in );
			   
				TiXmlPrinter printer;
				doc.Accept(&printer);
				String str = printer.CStr();
			
				stream->write( str );
			}
			catch( std::exception & e )
			{
				std::cerr << "Cannot export object to stream '"<<mName.data()<<"' because "<<String(e.what()).data();
				throw;
			}
			catch(...)
			{ throw; }
			return stream;
		}
	};
};

#endif